#ifndef GRAPHICS_SCENE_NODE_H
#define GRAPHICS_SCENE_NODE_H

#include <list>
#include <stack>

#include "boost/shared_ptr.hpp"

#include "graphics/result_codes.h"
#include "graphics/basic.h"
#include "graphics/camera.h"
#include "graphics/renderable.h"

namespace graphics{

class SceneNode {
public:
    SceneNode(const boost::shared_ptr<SceneNode> & parent) : parent_(parent){}
    virtual ~SceneNode(){}

    virtual int Render(const Camera & camera) = 0;   

    const boost::shared_ptr<SceneNode> & GetParent() const {return parent_;}
protected:
    boost::shared_ptr<SceneNode> parent_;
    static MatrixStack & matrix_stack_;
}
 
class InternalNode : SceneNode{
public:
    InternalNode(const boost::shared_ptr<SceneNode> & parent) : parent_(parent){}
    virtual ~LeafNode(){};
    
    int Render(const Camera & camera){
        if(child_)
            return child_->Render(camera);
        else return base::ER_NULL_POINTER;
    }
    virtual int SetChild(const boost::shared_ptr<SceneNode> & other) { 
        if(other.get() == NULL)
            return base::ER_NULL_POINTER;

        child_ = other;
        child_->parent_ = this;
        return base::OK_TRUE;
    } 

    const boost::shared_ptr<SceneNode> & GetChild() const {return child_;}
protected:
    boost::shared_ptr<SceneNode> child_;
}

class GroupNode : public SceneNode{
public:
    GroupNode(SceneNode * parent) : InternalNode(parent){}
    virtual ~GroupNode(){}

    virtual int Render(const Camera & camera) {
        std::list< boost::shared_ptr<SceneNode> >::iterator iterator;
        for(iterator = children_.begin(); iterator != children_.end(); iterator++)
            (*iterator)->Render(camera);        
    }

    int GetChildCount() const { 
        return children_.size(); 
    }

    //Recursive Counter.    
    // retruned value includes this node and all nodes below it
    int GetTreeSize() const {
        std::list<boost::shared_ptr<SceneNode>>::const_iterator iterator;
        int tree_size = 1;
        for(iterator = children_.begin(); iterator != children_.end(); iterator++)
            tree_size += (*iterator)->GetTreeSize();

        return tree_size;
    }

    
    int AddChild(boost::shared_ptr<SceneNode> & child) { 
        if(child.get() == NULL)
            return base::ER_NULL_POINTER;

        std::list<boost::shared_ptr<SceneNode>>::iterator iterator;
        iterator = std::find(children_.begin(), children_.end(), child);
        
        if(iterator == children_.end()) {
            children_.push_back(child);
            return base::OK_TRUE;
        }
        else return base::OK_FALSE;
    } 
    int RemoveChild(boost::shared_ptr<SceneNode> & child) {
        if(child.get() == NULL)
            return base::ER_NULL_POINTER;

        std::list< boost::shared_ptr<SceneNode> >::iterator iterator;        
        iterator = std::remove(children_.begin(), children_.end(), child);
        if(iterator != children_.end())
            return base::OK_TRUE;
        else return base::OK_FALSE;
    }
private:
    std::list<boost::shared_ptr<SceneNode>> children_;
};
 
class InstanceNode : public InternalNode {
public:
    InstanceNode(const boost::shared_ptr<SceneNode> & parent) : InternalNode(parent), transform_(cml::identity_4x4) {}

    int Render(const Camera & camera){
        if(!child_)
            return base::OK_FALSE;        
        else {
            SceneNode::matrix_stack_.Push(transform_);
            int result = InternalNode::Render();
            SceneNode::matrix_stack_.Pop();

            return result;
        }
    }
     
    const Matrix & GetTransform() const {return transform_;}
    void SetTransform(const Matrix & transform_) {transform_ = transform;}
private:
    Matrix transform_;
};

class RenderTargetNode : public SceneNode {
public:
    int Render(const Camera & camera){
        if(!render_target_)
            return base::ER_NULL_POINTER;

        //TODO: apply matrix_stack to renderer        
        return render_target_->Render();
    }
private:
    Renderable * render_target_
};
 
typedef RenderTargetNode LeafNode;
}
#endif //GRAPHICS_SCENE_NODES_H